package com.emeraldparser.symbol;

import java.util.Collection;
import java.util.Iterator;

import com.emeraldparser.symbol.SymbolClass.State;
import com.emeraldparser.util.Automaton;
import com.emeraldparser.util.Graph;
import com.emeraldparser.util.Graph.Edge;
import com.emeraldparser.util.SetGraph;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Iterables;

public class SymbolClasses {

	private static final Interner<SymbolClass> interner = Interners.newWeakInterner();

	private static final SymbolClass startOfInput = new StartOfInputSymbolClass();
	private static final SymbolClass endOfInput = new EndOfInputSymbolClass();

	private static SymbolClass intern(SymbolClass symbolClass) {
		return interner.intern(symbolClass);
	}

	/**
	 * Determines if the symbol class's automaton contains precisely two states:
	 * 
	 * <ul>
	 * <li>The state <em>before</em> consuming a symbol of type symbolClass</li>
	 * <li>The state <em>after</em> consuming a symbol of type symbolClass</li>
	 * </ul>
	 * 
	 * The "before" state must be the only "start" state. The "after" state must
	 * be the only "accept" state. The automaton should contain exactly one
	 * edge, more specifically the edge from the "before" state to the "after"
	 * state.
	 * 
	 * @param symbolClass
	 * @return
	 */
	public static boolean isAtomicAutomaton(SymbolClass symbolClass) {
		Automaton<? extends State, ? extends SymbolClass> automaton = symbolClass.toAutomaton();
		return isAtomicAutomaton(symbolClass, automaton);
	}

	public static boolean isAtomicAutomaton(SymbolClass symbolClass, Automaton<? extends State, ? extends SymbolClass> automaton) {
		Collection<? extends State> vertices = automaton.vertices();
		
		if (vertices.size() != 2) {
			return false;
		}
		
		
		
		Collection<? extends State> startVertices = automaton.getStartVertices();
		
		if (startVertices.size() != 1) {
			return false;
		}
		
		State startVertex = Iterables.getOnlyElement(startVertices);
		
		
		
		Collection<? extends State> acceptVertices = automaton.getAcceptVertices();
		
		if (acceptVertices.size() != 1) {
			return false;
		}
		
		State acceptVertex = Iterables.getOnlyElement(acceptVertices);
		
		
		
		Collection<? extends State> rejectVertices = automaton.getRejectVertices();
		
		if (!rejectVertices.isEmpty()) {
			return false;
		}
		
		
		Collection<? extends Edge<? extends State, ? extends SymbolClass>> edges = automaton.edges();
		
		if (edges.size() != 1) {
			return false;
		}
		
		Edge<? extends State, ? extends SymbolClass> edge = Iterables.getOnlyElement(edges);
		
		return Objects.equal(startVertex, edge.getSource())
		    && Objects.equal(acceptVertex, edge.getDestination())
		    && Objects.equal(symbolClass, edge.getLabel());
	}

	public static SymbolClass literal(char character) {
		return intern(new CharacterSymbolClass(character));
	}

	public static SymbolClass literal(CharSequence charSequence) {
		return intern(new CharSequenceSymbolClass(charSequence));
	}

	public static SymbolClass repeated(SymbolClass repeatedSymbolClass) {
		return intern(new RepetitionSymbolClass(repeatedSymbolClass));
	}

	public static SymbolClass oneOrMore(SymbolClass repeatedSymbolClass) {
		return intern(concat(repeatedSymbolClass, repeated(repeatedSymbolClass)));
	}

	public static SymbolClass all(SymbolClass... conjunctedSymbolClasses) {
		return intern(new ConjunctionSymbolClass(conjunctedSymbolClasses));
	}

	public static SymbolClass all(Iterable<? extends SymbolClass> conjunctedSymbolClasses) {
		return intern(new ConjunctionSymbolClass(conjunctedSymbolClasses));
	}

	public static SymbolClass all(ImmutableSet<? extends SymbolClass> conjunctedSymbolClasses) {
		return intern(new ConjunctionSymbolClass(conjunctedSymbolClasses));
	}

	public static SymbolClass any(SymbolClass... alternatedSymbolClasses) {
		return intern(new AlternationSymbolClass(alternatedSymbolClasses));
	}

	public static SymbolClass any(Iterable<? extends SymbolClass> alternatedSymbolClasses) {
		return intern(new AlternationSymbolClass(alternatedSymbolClasses));
	}

	public static SymbolClass any(ImmutableSet<? extends SymbolClass> alternatedSymbolClasses) {
		return intern(new AlternationSymbolClass(alternatedSymbolClasses));
	}

	public static SymbolClass concat(SymbolClass... concatenatedSymbolClasses) {
		return intern(new ConcatenationSymbolClass(concatenatedSymbolClasses));
	}

	public static SymbolClass concat(Iterable<? extends SymbolClass> concatenatedSymbolClasses) {
		return intern(new ConcatenationSymbolClass(concatenatedSymbolClasses));
	}

	public static SymbolClass concat(ImmutableList<? extends SymbolClass> concatenatedSymbolClasses) {
		return intern(new ConcatenationSymbolClass(concatenatedSymbolClasses));
	}

	public static SymbolClass named(String name) {
		return intern(new NamedSymbolClass(name));
	}

	public static SymbolClass range(char lowerBound, char upperBound) {
		return intern(new CharacterRangeSymbolClass(lowerBound, upperBound));
	}

	public static SymbolClass not(SymbolClass negatedSymbolClass) {
		return intern(new NegationSymbolClass(negatedSymbolClass));
	}

	public static SymbolClass optional(SymbolClass optionalSymbolClass) {
		return intern(new OptionalSymbolClass(optionalSymbolClass));
	}

	public static SymbolClass startOfInput() {
		return startOfInput;
	}

	public static SymbolClass endOfInput() {
		return endOfInput;
	}

	public static Graph<SymbolClass, ?> computeDependencies(Iterable<? extends SymbolClass> symbolClasses) {
		final Graph<SymbolClass, Object> dependencies = SetGraph.create();
		
		for (SymbolClass symbolClass : symbolClasses) {
			dependencies.addVertex(symbolClass);
			
			new SymbolClassScanner<SymbolClass, Void>() {

				@Override
				public Void visitAlternationSymbolClass(AlternationSymbolClass alternationSymbolClass, SymbolClass input) {
					dependencies.addVertex(alternationSymbolClass);
					dependencies.addDirectedEdge(input, alternationSymbolClass, null);
					return super.visitAlternationSymbolClass(alternationSymbolClass, alternationSymbolClass);
				}

				@Override
				public Void visitCharacterRangeSymbolClass(CharacterRangeSymbolClass characterRangeSymbolClass, SymbolClass input) {
					dependencies.addVertex(characterRangeSymbolClass);
					dependencies.addDirectedEdge(input, characterRangeSymbolClass, null);
					return super.visitCharacterRangeSymbolClass(characterRangeSymbolClass, characterRangeSymbolClass);
				}

				@Override
				public Void visitCharacterSymbolClass(CharacterSymbolClass characterSymbolClass, SymbolClass input) {
					dependencies.addVertex(characterSymbolClass);
					dependencies.addDirectedEdge(input, characterSymbolClass, null);
					return super.visitCharacterSymbolClass(characterSymbolClass, characterSymbolClass);
				}

				@Override
				public Void visitCharSequenceSymbolClass(CharSequenceSymbolClass charSequenceSymbolClass, SymbolClass input) {
					dependencies.addVertex(charSequenceSymbolClass);
					dependencies.addDirectedEdge(input, charSequenceSymbolClass, null);
					return super.visitCharSequenceSymbolClass(charSequenceSymbolClass, charSequenceSymbolClass);
				}

				@Override
				public Void visitConcatenationSymbolClass(ConcatenationSymbolClass concatenationSymbolClass, SymbolClass input) {
					dependencies.addVertex(concatenationSymbolClass);
					dependencies.addDirectedEdge(input, concatenationSymbolClass, null);
					return super.visitConcatenationSymbolClass(concatenationSymbolClass, concatenationSymbolClass);
				}

				@Override
				public Void visitConjunctionSymbolClass(ConjunctionSymbolClass conjunctionSymbolClass, SymbolClass input) {
					dependencies.addVertex(conjunctionSymbolClass);
					dependencies.addDirectedEdge(input, conjunctionSymbolClass, null);
					return super.visitConjunctionSymbolClass(conjunctionSymbolClass, conjunctionSymbolClass);
				}

				@Override
				public Void visitNamedSymbolClass(NamedSymbolClass namedSymbolClass, SymbolClass input) {
					dependencies.addVertex(namedSymbolClass);
					dependencies.addDirectedEdge(input, namedSymbolClass, null);
					return super.visitNamedSymbolClass(namedSymbolClass, namedSymbolClass);
				}

				@Override
				public Void visitNegationSymbolClass(NegationSymbolClass negationSymbolClass, SymbolClass input) {
					dependencies.addVertex(negationSymbolClass);
					dependencies.addDirectedEdge(input, negationSymbolClass, null);
					return super.visitNegationSymbolClass(negationSymbolClass, negationSymbolClass);
				}

				@Override
				public Void visitOptionalSymbolClass(OptionalSymbolClass optionalSymbolClass, SymbolClass input) {
					dependencies.addVertex(optionalSymbolClass);
					dependencies.addDirectedEdge(input, optionalSymbolClass, null);
					return super.visitOptionalSymbolClass(optionalSymbolClass, optionalSymbolClass);
				}

				@Override
				public Void visitRepetitionSymbolClass(RepetitionSymbolClass repetitionSymbolClass, SymbolClass input) {
					dependencies.addVertex(repetitionSymbolClass);
					dependencies.addDirectedEdge(input, repetitionSymbolClass, null);
					return super.visitRepetitionSymbolClass(repetitionSymbolClass, repetitionSymbolClass);
				}
				
			}.scan(symbolClass, symbolClass);
		}
		
		for (Iterator<Graph.Edge<SymbolClass, Object>> i = dependencies.edges().iterator(); i.hasNext();) {
			Graph.Edge<SymbolClass, ?> edge = i.next();
			
			if (Objects.equal(edge.getSource(), edge.getDestination())) {
				i.remove();
			}
		}
		
		return dependencies;
	}

}
